#include <QtGui>
#include <GL\GLee.h>
#include <QtOpenGL>
#include <cmath>
#include "hdviewport.h"
#include <QLabel>

HDViewport::HDViewport(QWidget* parent)
	: QGLWidget(parent)
{
	this->_mainMesh = 0;

	setFormat(QGLFormat(QGL::Rgba | QGL::DoubleBuffer | QGL::DepthBuffer));
	setAutoBufferSwap(true);
	setFocusPolicy(Qt::ClickFocus);

    zoom = -280.0f;
    rotationX = 30.0f;

    rotationY = 0.0f;
    view = HDViewport::Perspective;
    pntOffset = QPointF(0,0);


    wireframe = true;
    solid = false;

	first = second = 0;
	tmpVertex = new Vertex3f(0,0,0);
	selectedPlane = XZPlane;

	//to be removed
	isMouseDown = false;
}

void HDViewport::resizeGL( int /*width*/, int /*height*/ )
{
	SetupMatrices();
}

void HDViewport::paintGL()
{
	InitGL();

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glTranslatef(pntOffset.x(), pntOffset.y(), zoom);
	glRotatef(rotationX, 1.0, 0.0, 0.0);
	glRotatef(rotationY, 0.0, 1.0, 0.0);

	if(_mainMesh != 0)
	{
		if(wireframe && solid)
		{
			glDisable(GL_BLEND);
			_mainMesh->RenderMesh(GL_LINE, true);

			glEnable(GL_BLEND);		//we need it enabled for transparency
			glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
			_mainMesh->RenderMesh(GL_FILL, false);
		}
		else if(wireframe)
		{
			glDisable(GL_BLEND);
			_mainMesh->RenderMesh(GL_LINE, true);

			glEnable(GL_BLEND);		//we need it enabled for transparency
			glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
			//draw transparent mesh
			unsigned char alpha = _mainMesh->FillColor->A;
			_mainMesh->FillColor->A = 0;
			_mainMesh->RenderMesh(GL_FILL, false);
			_mainMesh->FillColor->A = alpha;
		}
		else if(solid)
		{
			glDisable(GL_BLEND);
			_mainMesh->RenderMesh(GL_FILL, true);
		}
	}


	for (int i = 0; i < Tools.count() ; i++)
	{
		glEnable(GL_BLEND);		//we need it enabled for transparency
		glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		//if(_tool->rawPosition)
		{
			SaveGLState();
			glMatrixMode(GL_MODELVIEW);
 			glLoadIdentity();
 
 			//glTranslatef(pntOffset.x(), pntOffset.y(), zoom);
 			glRotatef(rotationX, 1.0, 0.0, 0.0);
 			glRotatef(rotationY, 0.0, 1.0, 0.0);
 			//glTranslatef(pntOffset.x(), pntOffset.y(), zoom);


			//glMatrixMode(GL_MODELVIEW);
			//glPushMatrix();

			glGetFloatv(GL_MODELVIEW_MATRIX, Minv);
// 			GLfloat pos[4];
// 			pos[0] = _tool->Position->X;
// 			pos[1] = _tool->Position->Y;
// 			pos[2] = _tool->Position->Z;
// 			pos[3] = 1;

// 			GLfloat matPos[16];
// 			matPos[ 0] = 1; matPos[ 1] = 0; matPos[ 2] = 0; matPos[ 3] = -pntOffset.x();
// 			matPos[ 4] = 0; matPos[ 5] = 1; matPos[ 6] = 0; matPos[ 7] = -pntOffset.y();
// 			matPos[ 8] = 0; matPos[ 9] = 0; matPos[10] = 1; matPos[11] = -zoom;
// 			matPos[12] = 1; matPos[13] = 1; matPos[14] = 1; matPos[15] = 1;

// 			VMatMult(matPos, pos);
// 			VMatMult(Minv, pos);

// 			_tool->SetPosition(pos[0], pos[1], pos[2]);
// 			_tool->rawPosition = false;

			glMatrixMode(GL_MODELVIEW);
			glPopMatrix();

			RestoreGLState();
		}

		SaveGLState();
		//glLoadIdentity();
		glTranslatef(Tools.at(i)->Position->X , Tools.at(i)->Position->Y , Tools.at(i)->Position->Z );
		GLUquadric *quad = gluNewQuadric();      
		glColor4ub(255, 0, 0, 100);
		gluSphere(quad, Tools.at(i)->NeighborsLength + Tools.at(i)->Radius, 35, 35);
		RestoreGLState();
	}

}

void HDViewport::InitGL()
{
	glClearColor(0.5f, 0.50f, 0.5f, 1.0f);

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glLoadIdentity();

	glEnable (GL_DEPTH_TEST); //enable the depth testing
	glShadeModel (GL_SMOOTH); //set the shader to smooth shader

	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
}

void HDViewport::SetupMatrices()
{
    int w = width();
    int h = height() ? height() : 1;

    glViewport(0, 0, w, h);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    if(view == HDViewport::Perspective)
        gluPerspective(45.0f, (GLfloat)w/(GLfloat)h, 0.01f, 100000.0f);
    else
    {
        GLfloat x = GLfloat(w) / GLfloat(h);
        glOrtho(-4*x*(zoom/-10.0f), +4*x*(zoom/-10.0f),
                -4.0f*(zoom/-10.0f), +4.0f*(zoom/-10.0f),
                0.01f, 1000.0);
    }

    switch(view)
    {
        case HDViewport::Left:
        {
            rotationX = 0.0f;
            rotationY = 90.0f;
        }
        break;

        case HDViewport::Top:
        {
            rotationX = 90.0f;
            rotationY = 0.0f;
        }
        break;

        case HDViewport::Right:
        {
            rotationX = 0.0f;
            rotationY = -90.0f;
        }
        break;

        case HDViewport::Bottom:
        {
            rotationX = -90.0f;
            rotationY = 0.0f;
        }
        break;

        case HDViewport::Front:
        {
            rotationX = 0.0f;
            rotationY = 0.0f;
        }
        break;

        case HDViewport::Back:
        {
            rotationX = 0.0f;
            rotationY = 180.0f;
        }
        break;
    }

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(pntOffset.x(), pntOffset.y(), zoom);
    glRotatef(rotationX, 1.0, 0.0, 0.0);
    glRotatef(rotationY, 0.0, 1.0, 0.0);
}

void HDViewport::SaveGLState()
{
	glPushAttrib(GL_ALL_ATTRIB_BITS);

	glMatrixMode(GL_PROJECTION);
	glPushMatrix();

	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
}

void HDViewport::RestoreGLState()
{
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();

	glMatrixMode(GL_PROJECTION);
	glPopMatrix();

	glPopAttrib();
}

/************************************************************************/
/* Mouse Handling.                                                      */
/************************************************************************/
void HDViewport::mousePressEvent(QMouseEvent *event)
{
    pntMouseDown = event->pos();

	if (Qt::RightButton == (event->buttons() & Qt::RightButton))
	{
		setCursor(Qt::ClosedHandCursor);
	}
	else if(Qt::LeftButton == (event->buttons() & Qt::LeftButton) && _mainMesh != 0 && Tools.count() > 0)
	{
		isMouseDown = true;
		first = Tools.at(0)->SelectedVertex;
		pntA = pntB = pntMouseDown;

		if(Tools.at(0)->Area != 0 && Tools.at(0)->PrimaryMode == Tessellation)
		{
			_mainMesh->TesselateAroundVertices(Tools.at(0));
		}
		else if(Tools.at(0)->PrimaryMode == XFlattenning || Tools.at(0)->PrimaryMode == YFlattenning || Tools.at(0)->PrimaryMode == ZFlattenning)
		{
			if(isMouseDown)
			{
				if(Tools.at(0)->HasSelection)
				{
					_mainMesh->AffectSelectedArea1dGaussian(Tools.at(0), target);
				}
			}
		}
	}
}

void HDViewport::mouseReleaseEvent(QMouseEvent* event)
{
	unsetCursor();

	if(_mainMesh && Tools.count())
	{
		if(!(event->buttons() & Qt::LeftButton))
		{
			isMouseDown = false;

			if(Tools.at(0)->PrimaryMode == Selecting)
			{
				first = second = 0;
			}
		}
		update();
	}
}

void HDViewport::mouseMoveEvent(QMouseEvent *event)
{
	if(_mainMesh)
	{
		if (Qt::RightButton == (event->buttons() & Qt::RightButton))
		{
			GLfloat dx = GLfloat(event->x() - pntMouseDown.x());
			GLfloat dy = GLfloat(event->y() - pntMouseDown.y());

			if(event->modifiers() & Qt::AltModifier)
			{
				if(view != HDViewport::Perspective)
					view = HDViewport::Custom;

				rotationX += dy/2;
				rotationY += dx/2;
			}
			else
			{
				pntOffset += QPointF(dx * 8 *(zoom/-10.0f) / height(), dy * 8 *(zoom/10.0f) / height());
			}
			pntMouseDown = event->pos();
		}
		else if(Tools.count())
		{
			if(Tools.at(0)->PrimaryMode == Vertex)
			{
				if(isMouseDown)
				{
					pntA = pntB;
					pntB = event->pos();
					Vertex3f *newPosition = new Vertex3f(Tools.at(0)->SelectedVertex);

					switch (selectedPlane)
					{
					case XYPlane:
						newPosition->Z += (pntB.x() - pntA.x()) * zoom / (float)width();
						break;

					case XZPlane:
						newPosition->Y -= (pntB.y() - pntA.y()) * zoom / (float)(height()*4.0/3.0);
						break;

					case YZPlane:
						newPosition->X -= (pntB.x() - pntA.x()) * zoom / (float)width();
						break;
					}

					pntB = event->pos();

					if(Tools.at(0)->Area)
						_mainMesh->AffectSelectedArea1dGaussian(Tools.at(0), newPosition);
					delete newPosition;
				}
				else if(!Tools.at(0)->HasSelection) 
					UpdateSelectedVertex(event->pos());
			}
			else if(Tools.at(0)->PrimaryMode == XFlattenning || Tools.at(0)->PrimaryMode == YFlattenning || Tools.at(0)->PrimaryMode == ZFlattenning)
			{
				if(isMouseDown)
				{
					if(!Tools.at(0)->HasSelection)
					{
						UpdateSelectedVertex(event->pos());
						second = Tools.at(0)->SelectedVertex;
						_mainMesh->GetAffectedAreaInformation(Tools.at(0), first, second);
						_mainMesh->AffectSelectedArea1dGaussian(Tools.at(0), target);
						first = second;
					}
				}
			}
			else if(Tools.at(0)->PrimaryMode == Selecting)
			{
				UpdateSelectedVertex(event->pos());
				if(isMouseDown)
				{
					if(!Tools.at(0)->HasSelection)
						target = Tools.at(0)->SelectedVertex;
					second = Tools.at(0)->SelectedVertex;
					_mainMesh->GetAffectedAreaInformation(Tools.at(0), first, second);
					first = second;
				}
			}
		}
		updateGL();
	}
}

void HDViewport::UpdateSelectedVertex(QPoint position)
{
	if(_mainMesh != 0 && Tools.count() > 0)
	{
		GLint viewport[4];
		GLdouble modelview[16];
		GLdouble projection[16];
		GLfloat winX, winY, winZ;
		GLdouble x, y, z;

		glGetDoublev(GL_MODELVIEW_MATRIX, modelview );
		glGetDoublev(GL_PROJECTION_MATRIX, projection );
		glGetIntegerv(GL_VIEWPORT, viewport );

		winX = position.x();
		winY = viewport[3] - position.y();
		glReadPixels(position.x(), int(winY), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ);

		if(winZ == 1.0 || winZ == 0.0)
		{
			return;
		}
		else if(gluUnProject( winX, winY, winZ, modelview, projection, viewport, &x, &y, &z) == GL_TRUE)
		{
			Tools.at(0)->SetPosition(x, y, z);
			if(Tools.at(0)->PrimaryMode == Vertex)
			{
				Tools.at(0)->SelectedVertex = _mainMesh->GetNearestVertex(Tools.at(0)->Position);
				_mainMesh->GetAffectedAreaInformation(Tools.at(0));
			}
			else if(Tools.at(0)->PrimaryMode == XFlattenning || Tools.at(0)->PrimaryMode == YFlattenning || Tools.at(0)->PrimaryMode == ZFlattenning)
			{
				Tools.at(0)->SelectedVertex = _mainMesh->GetNearestVertex(Tools.at(0)->Position);
			}
			else if(Tools.at(0)->PrimaryMode == Selecting)
			{
				Tools.at(0)->SelectedVertex = _mainMesh->GetNearestVertex(Tools.at(0)->Position);
				if(!Tools.at(0)->HasSelection)
					_mainMesh->GetAffectedAreaInformation(Tools.at(0));
			}
		}
	}
}

void HDViewport::wheelEvent(QWheelEvent *event)
{
	if(hasFocus())
	{
		double numDegrees = -event->delta() / 8.0;
		double numSteps = numDegrees / 15.0;
		zoom *= std::pow(1.25, numSteps);
		updateGL();
	}
}

/************************************************************************/
/* Keyboard Handling.                                                   */
/************************************************************************/
void HDViewport::keyPressEvent(QKeyEvent *event)
{
    switch(event->key())
    {
		/*
        case Qt::Key_L:         // Left View
            view = Left;
			SetupMatrices();
        break;

        case Qt::Key_T:         // Top View
            view = Top;
			SetupMatrices();
        break;

        case Qt::Key_R:         // Right View
            view = Right;
			SetupMatrices();
        break;

        case Qt::Key_B:         // Back View
            view = Bottom;
			SetupMatrices();
        break;

        case Qt::Key_F:         // Front View
            view = Front;
			SetupMatrices();
        break;

        case Qt::Key_P:         // Perspective View
            view = Perspective;
			SetupMatrices();
        break;

        case Qt::Key_U:         // Custom View
            view = Custom;
			SetupMatrices();
        break;

        case Qt::Key_K:         // Back View
            view = Back;
			SetupMatrices();
        break;
		*/
		case Qt::Key_F3:
			solid = !solid;
			break;

        case Qt::Key_F4:
            wireframe = !wireframe;
        break;

		case Qt::Key_F5:
			selectedPlane = XYPlane;
			break;

		case Qt::Key_F6:
			selectedPlane = YZPlane;
			break;

		case Qt::Key_F7:
			selectedPlane = XZPlane;
			break;
		case Qt::Key_Z:
			{
				//'Button B'
				//switch tools
				if(Tools.count() > 0)
					Tools.at(0)->SwitchModes();
				/*
				if(Tools.at(0)->Area != 0)
					target = Tools.at(0)->Area->at(0);
				*/
			}
			break;
		case Qt::Key_X:
			{
				//'Button C'
				if(Tools.at(0) != 0)
					Tools.at(0)->FreeSelectedAreaInformation();
			}
			break;
		default:
			return;
    }
    updateGL();
}


//////////////////////////////////////////////////////////////////////////
// Public Getters and Setters
//////////////////////////////////////////////////////////////////////////

HDViewport::CameraType HDViewport::CameraView()
{
    return view;
}

void HDViewport::SetCameraView(CameraType view)
{
    this->view = view;
    update();
}


/*

void HDViewport::CreateGradient()
{
gradient.setCoordinateMode(QGradient::ObjectBoundingMode);
gradient.setCenter(0.50, 0.50);
gradient.setFocalPoint(0.50, 0.50);

gradient.setColorAt(0.0, QColor(158, 166, 172));
gradient.setColorAt(0.6, QColor(98, 104, 110));
gradient.setColorAt(0.8, QColor(75, 85, 95));

}

void HDViewport::LoadGLTextures()
{
QImage t;
QImage b;

if ( !b.load( "debug\\Crate.bmp" ) )
{
b = QImage( 16, 16, QImage::Format_ARGB32 );
b.fill( Qt::green );
}

t = convertToGLFormat( b );
glGenTextures( 1, &texture );
glBindTexture( GL_TEXTURE_2D, texture );
glTexImage2D( GL_TEXTURE_2D, 0, 3, t.width(), t.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, t.bits() );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
}


void HDViewport::paintEvent(QPaintEvent* )
{
QPainter painter;
painter.begin(this);

painter.drawPixmap(0, 0, width(), height(), pixBackground, 0, 0, pixBackground.width(), pixBackground.height());

makeCurrent();


SaveGLState();

InitGL();

SetupMatrices();

if(_mainMesh != 0)
{
Color4b *oldColor = 0;
if(solid)
{
_mainMesh->DrawingMode = GL_FILL;
//glEnable(GL_LIGHTING);
}
else
{
_mainMesh->DrawingMode = GL_LINE;
//glDisable(GL_LIGHTING);
}
_mainMesh->RenderMesh();

if(wireframe && solid)
{
int mode = _mainMesh->DrawingMode;
_mainMesh->DrawingMode = GL_LINE;
glDisable(GL_LIGHTING);
_mainMesh->RenderMesh();

_mainMesh->DrawingMode = mode;
}
}

RestoreGLState();

if (rubberBandIsShown) 
{
painter.setPen(QPen(Qt::white, 1, Qt::DashLine, Qt::SquareCap));
painter.setBrush(QBrush(QColor(92,128,255,20), Qt::SolidPattern));
painter.drawRect(QRect(pntA, pntB).normalized().adjusted(0, 0, -1, -1));
}

painter.drawPixmap(0, 0, pixForground);
painter.end();
}


void HDViewport::DrawBackground()
{
pixBackground = QPixmap(size());
pixBackground.fill(QColor(0,0,0,0));
QPainter painter(&pixBackground);

painter.setPen(Qt::NoPen);
painter.setBrush(gradient);
painter.drawRect(rect());
}

void HDViewport::DrawForeground()
{
pixForground = QPixmap(size());
pixForground.fill(QColor(0,0,0,0));

QPainter painter(&pixForground);
painter.setBrush(Qt::NoBrush);

if(!image.isNull())
{
int imgWidth, imgHeight;

if(width() > height())
{
imgWidth = width() / 4.0f;
imgHeight = imgWidth * image.height() / image.width();
}
else
{
imgHeight = height() / 4.0f;
imgWidth = imgHeight * image.width() / image.height();
}

const QRectF rect(width() - imgWidth - 3, height() - imgHeight - 3, imgWidth, imgHeight);
painter.drawImage(rect, image);
painter.drawRect(rect.adjusted(-1, -1, 0, 0));
}


painter.setPen(QColor("LightGray"));
painter.drawText(QRectF(5, 5, 100, 100), ENUM_NAME(HDViewport, CameraType, view));

if(hasFocus())
{
painter.setPen(QPen(Qt::yellow, 2));
}
else
{
painter.setPen(QPen(Qt::black, 2));
}

painter.drawRect(QRect(1, 1, width() - 2, height() - 2));
}



QImage HDViewport::Image()
{
return image;
}

void HDViewport::SetImage(QImage img)
{
image = img;
DrawForeground();
update();
}



int HDViewport::SelectObjectAt(const QPoint &pos)
{
makeCurrent();

SaveGLState();

InitGL();

SetupMatrices();

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
glDisable(GL_LIGHTING);
glDisable(GL_TEXTURE_2D);

DrawGLScene(true);

// get color information from frame buffer
unsigned char pixel[3];

GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
glReadPixels(pos.x(), viewport[3] - pos.y(), 1, 1, GL_RGB, GL_UNSIGNED_BYTE, pixel);

RestoreGLState();

if(pixel[0] == colBox[0] && pixel[1] == colBox[1] && pixel[2] == colBox[2])
{
return 1;
}
else if(pixel[0] == colSphere[0] && pixel[1] == colSphere[1] && pixel[2] == colSphere[2])
{
return 2;
}
else
{
return 0;
}
}

void HDViewport::focusInEvent(QFocusEvent * event)
{
DrawForeground();
update();
}

void HDViewport::focusOutEvent(QFocusEvent * event)
{
DrawForeground();
update();
}

*/
